package com.pijunn.webservice.fedex;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;

import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;

import com.fedex.rate.Address;
import com.fedex.rate.ClientDetail;
import com.fedex.rate.Dimensions;
import com.fedex.rate.DropoffType;
import com.fedex.rate.LinearUnits;
import com.fedex.rate.Money;
import com.fedex.rate.Notification;
import com.fedex.rate.NotificationSeverityType;
import com.fedex.rate.PackageRateDetail;
import com.fedex.rate.PackageSpecialServicesRequested;
import com.fedex.rate.PackagingType;
import com.fedex.rate.Party;
import com.fedex.rate.Payment;
import com.fedex.rate.PaymentType;
import com.fedex.rate.RatePortType;
import com.fedex.rate.RateReply;
import com.fedex.rate.RateReplyDetail;
import com.fedex.rate.RateRequest;
import com.fedex.rate.RateRequestType;
import com.fedex.rate.RateService;
import com.fedex.rate.RatedPackageDetail;
import com.fedex.rate.RatedShipmentDetail;
import com.fedex.rate.RequestedPackageLineItem;
import com.fedex.rate.RequestedShipment;
import com.fedex.rate.ServiceType;
import com.fedex.rate.ShipmentRateDetail;
import com.fedex.rate.Surcharge;
import com.fedex.rate.TransactionDetail;
import com.fedex.rate.VersionId;
import com.fedex.rate.WebAuthenticationCredential;
import com.fedex.rate.WebAuthenticationDetail;
import com.fedex.rate.Weight;
import com.fedex.rate.WeightUnits;
import com.pijunn.common.MailingAddress;
import com.pijunn.shipping.ShippingRate;


/** 
 * Sample code to call Rate Web Service with CXF 
 * <p>
 * com.fedex.rate.stub is generated via WSDL2Java, like this:<br>
 * <pre>
 * wsdl2java -compile -client -d /src/java/ -p com.fedex.rate RateService_v13.wsdl
 * </pre>
 * 
 * This sample code has been tested with JDK 6 and Apache CXF 2.6.5
 */
public class RateWebServiceClient {  
	private static String accountNumber;
	private static String meterNumber;
	private static String key;
	private static String password;
	
	//
	public static void main(String[] args) throws DatatypeConfigurationException {   
		// Build a RateRequest request object
		boolean getAllRatesFlag = false; // set to true to get the rates for different service types
	    RateRequest request = new RateRequest();
	    request.setClientDetail(createClientDetail());
        request.setWebAuthenticationDetail(createWebAuthenticationDetail());
        request.setReturnTransitAndCommit(true);
	    //
	    TransactionDetail transactionDetail = new TransactionDetail();
	    transactionDetail.setCustomerTransactionId("java sample - Rate Request"); // The client will get the same value back in the response
	    request.setTransactionDetail(transactionDetail);

        //
        VersionId versionId = new VersionId();
        versionId.setServiceId("crs");
        versionId.setMajor(13);
        versionId.setIntermediate(0);
        versionId.setMinor(0);
        request.setVersion(versionId);
        
        //
        RequestedShipment requestedShipment = new RequestedShipment();
        GregorianCalendar c = new GregorianCalendar();
        c.setTime(new Date());
        requestedShipment.setShipTimestamp(DatatypeFactory.newInstance().newXMLGregorianCalendar(c));
        requestedShipment.setDropoffType(DropoffType.STATION);
        if (! getAllRatesFlag) {
        	requestedShipment.setServiceType(ServiceType.PRIORITY_OVERNIGHT);
        	requestedShipment.setPackagingType(PackagingType.YOUR_PACKAGING);
        }
        
        
        Party shipper = new Party();
	    Address shipperAddress = new Address(); // Origin information
	    shipperAddress.getStreetLines().add("1316 Durango Ridge Rd");
	    shipperAddress.setCity("Durango");
	    shipperAddress.setStateOrProvinceCode("CO");
	    shipperAddress.setPostalCode("81301");
	    shipperAddress.setCountryCode("US");
        shipper.setAddress(shipperAddress);
        requestedShipment.setShipper(shipper);

	    //
        Party recipient = new Party();
	    Address recipientAddress = new Address(); // Destination information
	    recipientAddress.getStreetLines().add("8001 Pauline Vanier");
	    recipientAddress.setCity("COLLIERVILLE");
	    recipientAddress.setStateOrProvinceCode("TN");
	    recipientAddress.setPostalCode("38017");
	    recipientAddress.setCountryCode("US");
	    recipient.setAddress(recipientAddress);
	    requestedShipment.setRecipient(recipient);

	    //
	    Payment shippingChargesPayment = new Payment();
	    shippingChargesPayment.setPaymentType(PaymentType.SENDER);
	    requestedShipment.setShippingChargesPayment(shippingChargesPayment);

	    RequestedPackageLineItem rp = new RequestedPackageLineItem();
	    rp.setGroupPackageCount(new BigInteger("1"));
	    Weight weight = new Weight();
	    weight.setUnits(WeightUnits.LB);
	    weight.setValue(new BigDecimal(5.0));
	    rp.setWeight(weight);
	    //
	    Money money = new Money();
	    money.setCurrency("USD");
	    money.setAmount(new BigDecimal("100.00"));
	    rp.setInsuredValue(money);
	    //
	    Dimensions dimensions = new Dimensions();
	    dimensions.setHeight(new BigInteger("1"));
	    dimensions.setLength(new BigInteger("1"));
	    dimensions.setWidth(new BigInteger("1"));
	    dimensions.setUnits(LinearUnits.IN);
	    rp.setDimensions(dimensions);
	    PackageSpecialServicesRequested pssr = new PackageSpecialServicesRequested();
	    rp.setSpecialServicesRequested(pssr);
	    requestedShipment.getRequestedPackageLineItems().add(rp);

	    
	    requestedShipment.setPackageCount(new BigInteger("1"));
	    requestedShipment.getRateRequestTypes().add(RateRequestType.ACCOUNT);
	    request.setRequestedShipment(requestedShipment);
	    
	    //
		try {
			// Initialize the service
			URL.setURLStreamHandlerFactory(new ConfigurableStreamHandlerFactory("classpath", new com.pijunn.protocols.classpath.Handler(ClassLoader.getSystemClassLoader())));
			URL url = new URL("classpath:com/fedex/rate/RateService_v13.wsdl");
			RateService service = new RateService(url);
			RatePortType port;
			//
			port = service.getRateServicePort();
			// This is the call to the web service passing in a RateRequest and returning a RateReply
			RateReply reply = port.getRates(request); // Service call
			if (isResponseOk(reply.getHighestSeverity())) {
				writeServiceOutput(reply);
			} 
			printNotifications(reply.getNotifications());

		} catch (Exception e) {
		    e.printStackTrace();
		} 
	}
	
	public static RateReply getRate(MailingAddress from, MailingAddress to, ShippingRate rate, com.pijunn.common.enums.ServiceType type, BigDecimal insuredAmount) {
		// Build a RateRequest request object
		boolean getAllRatesFlag = false; // set to true to get the rates for different service types
	    RateRequest request = new RateRequest();
	    request.setClientDetail(createClientDetail());
        request.setWebAuthenticationDetail(createWebAuthenticationDetail());
        request.setReturnTransitAndCommit(true);
	    //
	    TransactionDetail transactionDetail = new TransactionDetail();
	    transactionDetail.setCustomerTransactionId("Rate Request for item return"); // The client will get the same value back in the response
	    request.setTransactionDetail(transactionDetail);

        //
        VersionId versionId = new VersionId();
        versionId.setServiceId("crs");
        versionId.setMajor(13);
        versionId.setIntermediate(0);
        versionId.setMinor(0);
        request.setVersion(versionId);
        
        //
        RequestedShipment requestedShipment = new RequestedShipment();
        GregorianCalendar c = new GregorianCalendar();
        c.setTime(new Date());
        try {
			requestedShipment.setShipTimestamp(DatatypeFactory.newInstance().newXMLGregorianCalendar(c));
		} catch (DatatypeConfigurationException e1) {
			// This should not happen as long as we use the current time
			e1.printStackTrace();
		}
        requestedShipment.setDropoffType(DropoffType.STATION);
        if (! getAllRatesFlag) {
        	requestedShipment.setServiceType(ServiceType.valueOf(type.name()));
        	requestedShipment.setPackagingType(PackagingType.YOUR_PACKAGING);
        }
        
        
        Party shipper = new Party();
	    Address shipperAddress = new Address(); // Origin information
	    shipperAddress.getStreetLines().add(from.getAddress1());
	    if (from.getAddress2() != null) shipperAddress.getStreetLines().add(from.getAddress2());
	    shipperAddress.setCity(from.getCity());
	    shipperAddress.setStateOrProvinceCode(from.getState());
	    shipperAddress.setPostalCode(from.getZipCode());
	    shipperAddress.setCountryCode(from.getCountry());
        shipper.setAddress(shipperAddress);
        requestedShipment.setShipper(shipper);

	    //
        Party recipient = new Party();
	    Address recipientAddress = new Address(); // Destination information
	    recipientAddress.getStreetLines().add(to.getAddress1());
	    if (to.getAddress2() != null)  recipientAddress.getStreetLines().add(to.getAddress2());
	    recipientAddress.setCity(to.getCity());
	    recipientAddress.setStateOrProvinceCode(to.getState());
	    recipientAddress.setPostalCode(to.getZipCode());
	    recipientAddress.setCountryCode(to.getCountry());
	    recipient.setAddress(recipientAddress);
	    requestedShipment.setRecipient(recipient);

	    //
	    Payment shippingChargesPayment = new Payment();
	    shippingChargesPayment.setPaymentType(PaymentType.SENDER);
	    requestedShipment.setShippingChargesPayment(shippingChargesPayment);

	    RequestedPackageLineItem rp = new RequestedPackageLineItem();
	    rp.setGroupPackageCount(new BigInteger("1"));
	    Weight weight = new Weight();
	    weight.setUnits(WeightUnits.LB);
	    weight.setValue(new BigDecimal(rate.getEmptyWeight()));
	    rp.setWeight(weight);
	    //
	    Money money = new Money();
	    money.setCurrency("USD");
	    money.setAmount(insuredAmount);
	    rp.setInsuredValue(money);
	    //
	    Dimensions dimensions = new Dimensions();
	    dimensions.setHeight(new BigInteger(""+rate.getDimensions().getHeight()));
	    dimensions.setLength(new BigInteger(""+rate.getDimensions().getDepth()));
	    dimensions.setWidth(new BigInteger(""+rate.getDimensions().getWidth()));
	    dimensions.setUnits(LinearUnits.IN);
	    rp.setDimensions(dimensions);
	    PackageSpecialServicesRequested pssr = new PackageSpecialServicesRequested();
	    rp.setSpecialServicesRequested(pssr);
	    requestedShipment.getRequestedPackageLineItems().add(rp);

	    
	    requestedShipment.setPackageCount(new BigInteger("1"));
	    requestedShipment.getRateRequestTypes().add(RateRequestType.ACCOUNT);
	    request.setRequestedShipment(requestedShipment);
	    
	    //
		try {
			// Initialize the service
			URL.setURLStreamHandlerFactory(new ConfigurableStreamHandlerFactory("classpath", new com.pijunn.protocols.classpath.Handler(ClassLoader.getSystemClassLoader())));
			URL url = new URL("classpath:com/fedex/rate/RateService_v13.wsdl");
			RateService service = new RateService(url);
			RatePortType port;
			//
			port = service.getRateServicePort();
			// This is the call to the web service passing in a RateRequest and returning a RateReply
			RateReply reply = port.getRates(request); // Service call
			if (isResponseOk(reply.getHighestSeverity())) {
				writeServiceOutput(reply);
			} 
			
			return reply;

		} catch (Exception e) {
		    e.printStackTrace();
		} 
		
		return null;
	}
	
	public static void writeServiceOutput(RateReply reply) {
		List<RateReplyDetail> rrds = reply.getRateReplyDetails();
		for (RateReplyDetail rrd : rrds) {
			
			//print("\nService type", rrd.getServiceType());
			print("Packaging type", rrd.getPackagingType());
			//print("Delivery DOW", rrd.getDeliveryDayOfWeek());
			int month = rrd.getDeliveryTimestamp().getMonth();
			int date = rrd.getDeliveryTimestamp().getDay();
			int year = rrd.getDeliveryTimestamp().getYear();
			String delDate = new String(month + "/" + date + "/" + year);
			print("Delivery date", delDate);
			//print("Calendar DOW", rrd.getDeliveryTimestamp().get);

			List<RatedShipmentDetail> rsds = rrd.getRatedShipmentDetails();
			for (RatedShipmentDetail rsd : rsds) {
				print("RatedShipmentDetail ", rsd);
				
				ShipmentRateDetail srd = rsd.getShipmentRateDetail();
				print("  Rate type", srd.getRateType());
				printWeight("  Total Billing weight", srd.getTotalBillingWeight());
				printMoney("  Total surcharges", srd.getTotalSurcharges());
				printMoney("  Total net charge", srd.getTotalNetCharge());

				List<RatedPackageDetail> rpds = rsd.getRatedPackages();
				if (rpds != null && rpds.size() > 0) {
					print("  RatedPackageDetails", "");
					for (RatedPackageDetail rpd : rpds) {
						print("  RatedPackageDetail ", "");
						
						PackageRateDetail prd = rpd.getPackageRateDetail();
						if (prd != null) {
							printWeight("    Billing weight", prd.getBillingWeight());
							printMoney("    Base charge", prd.getBaseCharge());
							List<Surcharge> surcharges = prd.getSurcharges();
							if (surcharges != null && surcharges.size() > 0) {
								for (Surcharge surcharge : surcharges) {
									printMoney("    " + surcharge.getDescription() + " surcharge", surcharge.getAmount());
								}
							}
						}
					}
				}
			}
			System.out.println("");
		}
	}
	
	private static ClientDetail createClientDetail() {
        ClientDetail clientDetail = new ClientDetail();
        String accountNumber = System.getProperty("accountNumber");
        String meterNumber = System.getProperty("meterNumber");
        
        //
        // See if the accountNumber and meterNumber properties are set,
        // if set use those values, otherwise default them to static values
        //
        if (accountNumber == null) {
        	accountNumber = RateWebServiceClient.accountNumber; // clients account number
        }
        if (meterNumber == null) {
        	meterNumber = RateWebServiceClient.meterNumber; // clients meter number
        }
        clientDetail.setAccountNumber(accountNumber);
        clientDetail.setMeterNumber(meterNumber);
        return clientDetail;
	}
	
	private static WebAuthenticationDetail createWebAuthenticationDetail() {
        WebAuthenticationCredential wac = new WebAuthenticationCredential();
        String key = System.getProperty("key");
        String password = System.getProperty("password");
        
        //
        // See if the key and password properties are set,
        // if set use those values, otherwise default them to Static values
        //
        if (key == null) {
        	key = RateWebServiceClient.key; // clients key
        }
        if (password == null) {
        	password = RateWebServiceClient.password; // clients password
        }
        wac.setKey(key);
        wac.setPassword(password);
        WebAuthenticationDetail wad = new WebAuthenticationDetail();
        wad.setUserCredential(wac);
		return wad;
	}
	
	private static void printNotifications(List<Notification> list) {
		System.out.println("Notifications:");
		if (list == null || list.size() == 0) {
			System.out.println("  No notifications returned");
		}
		for (Notification n : list){
			System.out.print("  Notification no. : ");
			if (n == null) {
				System.out.println("null");
				continue;
			} else {
				System.out.println("");
			}
			NotificationSeverityType nst = n.getSeverity();

			System.out.println("    Severity: " + (nst == null ? "null" : nst.value()));
			System.out.println("    Code: " + n.getCode());
			System.out.println("    Message: " + n.getMessage());
			System.out.println("    Source: " + n.getSource());
		}
	}
	
	private static boolean isResponseOk(NotificationSeverityType notificationSeverityType) {
		if (notificationSeverityType == null) {
			return false;
		}
		if (notificationSeverityType.equals(NotificationSeverityType.WARNING) ||
			notificationSeverityType.equals(NotificationSeverityType.NOTE)    ||
			notificationSeverityType.equals(NotificationSeverityType.SUCCESS)) {
			return true;
		}
 		return false;
	}
	
	private static void print(String msg, Object obj) {
		if (msg == null || obj == null) {
			return;
		}
		System.out.println(msg + ": " + obj.toString());
	}
	
	private static void printMoney(String msg, Money money) {
		if (msg == null || money == null) {
			return;
		}
		System.out.println(msg + ": " + money.getAmount() + " " + money.getCurrency());
	}
	
	private static void printWeight(String msg, Weight weight) {
		if (msg == null || weight == null) {
			return;
		}
		System.out.println(msg + ": " + weight.getValue() + " " + weight.getUnits());
	}

	public static void setAccountNumber(String accountSetting) {
		accountNumber = accountSetting;
	}
	
	public static void setMeterNumber(String meterSetting) {
		meterNumber = meterSetting;
	}
	
	public static void setKey(String keySetting) {
		key = keySetting;
	}
	
	public static void setPassword(String passwordSetting) {
		password = passwordSetting;
	}

}
